import json
import re
import cn2an
import itertools
import jieba
jieba.load_userdict("module/intention_classifier/mdxdict.txt")
class IntentionClassfier:
    def __init__(self, data_source_path):
        #从配置文件中读取，初始化全局数据
        self.measure_dict={}
        self.agg_mode_dict={}
        with open(data_source_path, 'r', encoding='utf-8') as json_file:
            data=json.load(json_file)
            # header=data[0]['header']+data[1]['header']
            header=data[1]['header']
            for item in header:
                if item['fieldType']=='MEASURE':
                    self.measure_dict[item['id']]=True
                else:
                    self.measure_dict[item['id']]=False
                if 'aggregationMode' in item.keys():
                    self.agg_mode_dict[item['id']]=item['aggregationMode'] if item['aggregationMode']!='sum' else 'SUM'
        with open('module/intention_classifier/rule.json', 'r', encoding='utf-8') as json_file:
            data=json.load(json_file)
            self.sum_key_list=data['agg']['SUM']
            self.avg_key_list=data['agg']['AVG']
            self.count_key_list=data['agg']['COUNT']
            self.list_key_list=data['agg']['LIST']
            self.max_key_pattern=re.compile(data['limit']['MAX'])
            self.min_key_pattern=re.compile(data['limit']['MIN'])
            self.limit_1_key_pattern=re.compile(data['limit']['LIMIT1'])
            self.limit_many_key_pattern=re.compile(data['limit']['LIMITMANY'])
            target_dict=data['mdx']
            self.synonyms2mdx={}
            for key,value in target_dict.items():
                for word in value:
                    self.synonyms2mdx[word]=key
    
    def predict(self, dimension_dict,dim_pos):
        """
        意图识别模块的对外接口，接收从维度识别模块输出的维度字典和位置列表
        返回包括agg,limit,mdx,measure等字段的新字典
        对于存在agg冲突的情况，会拆分成多个新字典再返回
        """
        ques=dimension_dict['question']
        dim_pos={item[0]:item[1] for item in dim_pos}
        sel_name_list=[sel['name'] for sel in dimension_dict['sel']]
        dim_pos=[dim_pos[sel_name] for sel_name in sel_name_list]#根据name重新排序位置列表，使其与name对齐
        sel_id_list=[sel['id'] for sel in dimension_dict['sel']]
        agg_list=self.agg_key_word_match_v2(ques,sel_id_list,sel_name_list,dim_pos)
        mdx_list=self.operation_predict_v2(ques,sel_id_list,sel_name_list,dim_pos)
        measure_list=self.search_measure([sel['id'] for sel in dimension_dict['sel']])
        limit_prediction=self.limit_predict(ques)
        limit_list=[] if limit_prediction==0 else [["LIMIT",str(limit_prediction)]]
        ans=[]
        for key,value in agg_list.items():
            new_dict={}
            new_dict['question']=key
            new_dict['agg']=value
            new_dict['limit']=limit_list
            new_dict['mdx']=mdx_list
            new_dict['sel']=dimension_dict['sel'][:]
            new_dict['measure']=measure_list
            ans.append(new_dict)
            print(key)
        return ans

    def agg_key_word_match_v2(self, ques,id_list,name_list,pos_list):
        """
        根据输入的ques，预测每个sel的agg
        如果一个sel存在多个预测的agg，会进行拆分，拆分后每个新的ques会对应一个新的agg列表
        """
        assert len(id_list)==len(name_list) and len(id_list)==len(pos_list)
        #每个sel项构造一个字典(id,name,pos,is_measure,agg)
        all_info_tuples=[{'id':id_list[i],'name':name_list[i],'pos':pos_list[i],'is_measure':self.get_measure(id_list[i]),'agg':[]} for i in range(len(id_list))]
        #按照位置顺序排序
        all_info_tuples=sorted(all_info_tuples,key=lambda x:x['pos'][1])
        #获取每种agg关键词的下标
        sum_key_index=self.sub_str_match(ques,self.sum_key_list,pos_list)
        avg_key_index=self.sub_str_match(ques,self.avg_key_list,pos_list)
        count_key_index=self.sub_str_match(ques,self.count_key_list,pos_list)
        list_key_index=self.sub_str_match(ques,self.list_key_list,pos_list)
        print('sum_key:',sum_key_index)
        print('avg_key:',avg_key_index)
        
        #avg绑定
        for avg_index in avg_key_index:
            find_sel_index=-1#对应all_info_tuples里的第几个
            for (sel_index, sel_info) in enumerate(all_info_tuples):
                if sel_info['is_measure']:#只会绑定给measure类型
                    if avg_index[0]>sel_info['pos'][0]:#最后一个小于count_index的sel_index
                        find_sel_index=sel_index
            if find_sel_index!=-1:#如果成功匹配到了关键词
                if 'AVG' not in all_info_tuples[find_sel_index]['agg']:#并且当前agg还没有被绑定给该列
                    all_info_tuples[find_sel_index]['agg'].append(('AVG',avg_index))#执行绑定操作
        #sum绑定
        for sum_index in sum_key_index:
            find_sel_index=-1#对应all_info_tuples里的第几个
            for (sel_index, sel_info) in enumerate(all_info_tuples):
                if sel_info['is_measure']:#只会绑定给measure类型
                    if sum_index[0]>sel_info['pos'][0]:#最后一个小于count_index的sel_index
                        find_sel_index=sel_index
            if find_sel_index!=-1:#如果成功匹配到了关键词
                if 'SUM' not in all_info_tuples[find_sel_index]['agg']:#并且当前agg还没有被绑定给该列
                    all_info_tuples[find_sel_index]['agg'].append(('SUM',sum_index))#执行绑定操作
        #count绑定
        for count_index in count_key_index:
            find_sel_index=-1#对应all_info_tuples里的第几个
            for (sel_index, sel_info) in enumerate(all_info_tuples):
                if not sel_info['is_measure']:#只会绑定给非measure类型
                    if not any(time_level in sel_info['name'] for time_level in ['年','月','日','季']):
                        if count_index[0]>sel_info['pos'][0]:#最后一个小于count_index的sel_index
                            find_sel_index=sel_index
            if find_sel_index!=-1:#如果成功匹配到了关键词
                if 'distinct-count' not in all_info_tuples[find_sel_index]['agg']:#并且当前agg还没有被绑定给该列
                    all_info_tuples[find_sel_index]['agg'].append(('distinct-count',count_index))#执行绑定操作
        #list绑定
        for list_index in list_key_index:
            find_sel_index=-1#对应all_info_tuples里的第几个
            for (sel_index, sel_info) in enumerate(all_info_tuples):
                if not sel_info['is_measure']:#只会绑定给非measure类型
                    if list_index[0]>sel_info['pos'][0]:#最后一个小于count_index的sel_index
                        find_sel_index=sel_index
            if find_sel_index!=-1:#如果成功匹配到了关键词
                if '' not in all_info_tuples[find_sel_index]['agg']:#并且当前agg还没有被绑定给该列
                    all_info_tuples[find_sel_index]['agg'].append(('',list_index))#执行绑定操作
        #还有一个维度后面跟着“和”、“，”、“、”的情况也视为list
        for (sel_index, sel_info) in enumerate(all_info_tuples):
            if not sel_info['is_measure'] and sel_info['pos'][1]< len(ques) and any([item==ques[sel_info['pos'][1]] for item in ['和',',','、']]):
                if '' not in all_info_tuples[sel_index]['agg']:#当前agg还没有被绑定给该列
                    all_info_tuples[sel_index]['agg'].append(('',(sel_info['pos'][1],sel_info['pos'][1]+1)))
        
        #没有聚合函数的给一个默认聚合函数
        for (sel_index, sel_info) in enumerate(all_info_tuples):
            if len(sel_info['agg'])==0:
                if sel_info['id'] in self.agg_mode_dict.keys():
                    all_info_tuples[sel_index]['agg'].append((self.agg_mode_dict[sel_info['id']],(0,0)))
                else:
                    if sel_info['id'] in self.measure_dict.keys() and self.measure_dict[sel_info['id']]:
                        if '平均' in sel_info['name']:
                            all_info_tuples[sel_index]['agg'].append(('AVG',(0,0)))
                        else:
                            all_info_tuples[sel_index]['agg'].append(('SUM',(0,0)))
                    else:
                        all_info_tuples[sel_index]['agg'].append(('',(0,0)))

        #现在的agg里的格式是(agg,(start,end))，要改成(agg,[(other agg start,other agg end)...])，便于后续切割ques
        for i in range(len(all_info_tuples)):
            new_agg=[(single_agg[0],[]) for single_agg in all_info_tuples[i]['agg']]
            for j in range(len(all_info_tuples[i]['agg'])):
                for k in range(len(all_info_tuples[i]['agg'])):
                    if k!=j:
                        new_agg[j][1].append(all_info_tuples[i]['agg'][k][1])
            all_info_tuples[i]['agg']=new_agg
        

        #构造一个id到agg的映射字典，方便还原本来的顺序
        origin_agg_list=[]
        id_agg_dict={all_info_tuples[i]['id']:all_info_tuples[i]['agg'] for i in range(len(all_info_tuples))}
        #还原本来的顺序
        for sel_id in id_list:
            origin_agg_list.append(id_agg_dict[sel_id])
        #不同列之间的agg排列组合
        combinations = itertools.product(*origin_agg_list)
        combinations=list(combinations)
        combinations=[list(combines) for combines in combinations]
        #对每种排列组合，去除掉不在这个组合里面的关键词
        ans_ques_list=[]
        ans_agg_list=[]
        for combines in combinations:
            tmp_word_pos_list=[]
            tmp_ans_agg_list=[item[0] for item in combines]
            for item in combines:
                tmp_word_pos_list.extend(item[1])
            ans_agg_list.append(tmp_ans_agg_list)
            tmp_ques=self.get_outside_str(ques,tmp_word_pos_list)
            ans_ques_list.append(tmp_ques)
        #返回ques-agg的键值对
        ans={ans_ques_list[i]:ans_agg_list[i] for i in range(len(ans_ques_list))}
        return ans
    
    def sub_str_match(self, parent_str,key_word_list,pos_list):
        """
        从parent_str中识别key_word_list中的每个词，并且返回它们的起始位置和结束位置，保证它们不被包含在pos_list中的
        """
        ans=[]
        for word in key_word_list:
            
            position = parent_str.find(word)
            while position != -1:
                if not any([pos_list[i][0]<=position<pos_list[i][1] for i in range(len(pos_list))]):#防止类似于“个数”出现在“合同个数”里面的情况
                    ans.append((position,position+len(word)))
                position = parent_str.find(word, position + 1)
        return ans
    def get_measure(self,sel_id):
        """
        根据给定sel的id返回其是否为measure类型
        """
        if sel_id in self.measure_dict.keys():
            is_measure=self.measure_dict[sel_id]
            if is_measure:
                return True
        return False
    def get_outside_str(self,origin_str,range_list):
        """
        从origin_str中提取出不包含在range_list中的部分
        """
        if len(range_list)==0:
            return origin_str
        range_list=sorted(range_list,key=lambda x:x[0])
        ans=[]
        for (index, item) in enumerate(range_list):
            if index==0:
                ans.append(origin_str[:item[0]])
                if index==len(range_list)-1:
                    ans.append(origin_str[item[1]:])
            elif index==len(range_list)-1:
                ans.append(origin_str[range_list[index-1][1]:item[0]])
                ans.append(origin_str[item[1]:])
            else:
                if range_list[index-1][1]>item[0]:
                    return origin_str
                ans.append(origin_str[range_list[index-1][1]:item[0]])
        return ''.join(ans)
    def limit_predict(self,ques):
        """
        根据输入的ques，预测其limit值
        """
        limit=0
        ques=cn2an.transform(ques)
        m=self.limit_1_key_pattern.search(ques)
        if m:
            limit=1
        if limit==0:
            m=self.limit_many_key_pattern.search(ques)
            if m:
                for group in m.groups():
                    if group:
                        limit=group
        return int(limit)
    def search_measure(self,sel_id_list):
        """
        根据给定的sel列表，查询其中的measure类型的sel，并以列表形式返回
        """
        measure_list=[]
        for sel_id in sel_id_list:
            if sel_id in self.measure_dict.keys():
                is_measure=self.measure_dict[sel_id]
                if is_measure:
                    measure_list.append(sel_id)
        return measure_list
    def operation_predict_v2(self, ques,id_list,name_list,pos_list):
        """
        根据输入的ques，预测所有的mdx操作，实现逻辑与agg识别类似，但是一个列可以有多个mdx操作
        """
        assert len(id_list)==len(name_list) and len(id_list)==len(pos_list)
        mdx_list=[]
        
        ques_words = jieba.cut(ques)
        ques_words=list(ques_words)
        ques_words_pos=[0]
        pre_pos=0
        for i in range(1,len(ques_words)):#获取分词后每个词的下标
            ques_words_pos.append(ques_words_pos[-1]+len(ques_words[i]))
        time_level_id=''
        tmp_time_level=0
        for i in range(len(name_list)):
            if name_list[i]=='日':
                time_level_id=id_list[i]
                break
            elif name_list[i]=='月':
                time_level_id=id_list[i]
                tmp_time_level=3
            elif name_list[i]=='季' and tmp_time_level<=1:
                time_level_id=id_list[i]
                tmp_time_level=2
            elif name_list[i]=='年' and tmp_time_level==0:
                time_level_id=id_list[i]
                tmp_time_level=1
        #每个sel项构造一个字典(id,name,pos,is_measure,agg)
        all_info_tuples=[{'id':id_list[i],'name':name_list[i],'pos':pos_list[i],'is_measure':self.get_measure(id_list[i]),'mdx':[]} for i in range(len(id_list))]
        all_info_tuples=sorted(all_info_tuples,key=lambda x:x['pos'][1])
        for index,ques_word in enumerate(ques_words):
            if ques_word in self.synonyms2mdx.keys():
                word2mdx=self.synonyms2mdx[ques_word]
                tmp_pos=ques_words_pos[index]
                find_sel_index=-1#对应all_info_tuples里的第几个
                for (sel_index, sel_info) in enumerate(all_info_tuples):
                    if sel_info['is_measure']:
                        if tmp_pos>sel_info['pos'][0]:#最后一个小于count_index的sel_index
                            find_sel_index=sel_index
                if find_sel_index!=-1:
                    all_info_tuples[find_sel_index]['mdx'].append(word2mdx)
        for info_term in all_info_tuples:
            for mdx_term in info_term['mdx']:
                mdx_list.append(self.combine_func(info_term['id'],info_term['name'],time_level_id,mdx_term))#如果后期还需要对单个mdx的信息进行修改就在此处修改
        return mdx_list
    def combine_func(self, measure_id,measure_name,time_level_id,op_name):
        ans_dict={}
        ans_dict['function_name']=op_name
        ans_dict['id']=measure_name+op_name
        ans_dict['name']=measure_name+op_name
        ans_dict['type']='MEASURE'
        ans_dict['member_amount']=0
        ans_dict['param']={}
        ans_dict['param']['time_id']=time_level_id
        ans_dict['param']['measure_id']=measure_id
        return ans_dict

if __name__ == '__main__':
    int_detect = IntentionClassfier('../../metaData/data_dict.json')
    query = "今年广州和深圳的合同金额"
    # output, choices, like_dict, dim_pos = dim_detect.detect(query)
    output = int_detect.predict(query, output)
    print(output)
    print(choices)
    print(like_dict)
    print(dim_pos)